<template>
  <div class="upload-file">
    <el-upload
      ref="fileUpload"
      :multiple="ismultiple"
      action=""
      :before-upload="handleBeforeUpload"
      :file-list="fileList"
      :limit="limit"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      :http-request="handleUpload"
      :show-file-list="false"
      :headers="headers"
      class="upload-file-uploader"
      :disabled="disabled"
      :data="extraData">
      <!-- 上传按钮 -->
      <el-button size="mini" type="primary">选取文件</el-button>
      <!-- 上传提示 -->
      <div v-if="showTip" slot="tip" class="el-upload__tip">
        请上传
        <template v-if="limit">
          最多不超过 <b style="color: #f56c6c">{{ limit }}个</b>
        </template>
        <template v-if="fileSize">
          大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
        </template>
        <template v-if="fileType">
          格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
        </template>
        的文件
      </div>
    </el-upload>

    <!-- 文件列表 -->
    <transition-group
      v-if="showFile"
      class="upload-file-list el-upload-list el-upload-list--text"
      name="el-fade-in-linear"
      tag="ul">
      <li
        v-for="(file, index) in fileList"
        :key="file.url"
        class="el-upload-list__item ele-upload-list__item-content">
        <el-link :href="file.url" :underline="false" target="_blank">
          <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
        </el-link>
        <div v-if="!disabled" class="ele-upload-list__item-content-action">
          <el-link v-if="uploadType === 'video'" :underline="false" type="primary" @click="handlePreview(file)">
            预览
          </el-link>
          <el-link :underline="false" type="danger" @click="handleDelete(index)">
            删除
          </el-link>
        </div>
      </li>
    </transition-group>

    <!-- 视频预览dialog -->
    <el-dialog
      :visible.sync="dialogVisible"
      title="预览"
      width="800"
      append-to-body>
      <video ref="videoPlay" width="100%" height="500" autoplay muted loop>
        <source :src="dialogVideoUrl" type="video/mp4">
      </video>
    </el-dialog>
  </div>
</template>

<script>
// import { getToken } from '@/utils/auth';
import { getFileMd5 } from '@/utils/publicFunc';
import api from '@/api/index';

export default {
  name: 'FileUpload',
  props: {
    // 值
    value: [String, Object, Array],
    // 是否支持一次性选择多个文件
    ismultiple: {
      type: Boolean,
      default: false
    },
    // 数量限制
    limit: {
      type: Number,
      default: 5
    },
    // 大小限制(MB)
    fileSize: {
      type: [Number, Object],
      default: 5
    },
    // 文件类型, 例如['png', 'jpg', 'jpeg']
    fileType: {
      type: Array,
      default: () => ['doc', 'xls', 'ppt', 'txt', 'pdf']
    },
    // 上传类型(视频或其它)
    uploadType: {
      type: String,
      default: ''
    },
    // 是否显示提示
    isShowTip: {
      type: Boolean,
      default: true
    },
    // 是否显示文件列表
    showFile: {
      type: Boolean,
      default: true
    },
    // 禁用
    disabled: {
      type: Boolean,
      default: false
    },
    // 业务接口
    interface: {
      type: String,
      default: ''
    },
    // 额外数据
    extraData: {
      type: Object,
      default: () => {}
    }
  },
  data() {
    return {
      number: 0,
      uploadList: [],
      dialogVideoUrl: '',
      dialogVisible: false,
      uploadFileUrl: process.env.VUE_APP_BASE_API + this.interface, // 上传文件服务器地址
      // headers: {
      //   Authorization: 'Bearer ' + getToken()
      // },
      fileList: [],
      typeText: {
        dom: {
          type: ['jpeg', 'jpg', 'gif', 'png', 'xls', 'xlsx', 'doc', 'docx', 'ppt', 'pptx', 'pdf', 'txt'],
          txt: '【图文、文档】'
        },
        media: {
          type: ['avi', 'mp4', 'mov', 'wav', 'mp3'],
          txt: '【音频、视频】'
        }
      },
      // 标志文件是否为大文件
      isLarge: false
    };
  },
  computed: {
    // 是否显示提示
    showTip() {
      return this.isShowTip && ((this.fileSize &&
      typeof (this.fileSize) === 'number') &&
      this.fileType);
    }
  },
  watch: {
    value: {
      handler(val) {
        if (val) {
          let temp = 1;
          // 首先将值转为数组
          const list = Array.isArray(val) ? val : this.value.split(',');
          // 然后将数组转为对象数组
          this.fileList = list.map((item) => {
            if (typeof item === 'string') {
              item = { name: item, url: item };
            }
            item.uid = item.uid || new Date().getTime() + temp++;
            return item;
          });
        } else {
          this.fileList = [];
          return [];
        }
      },
      deep: true,
      immediate: true
    }
  },
  methods: {
    // 上传前校检格式和大小
    handleBeforeUpload(file) {
      const fileName = file.name.split('.');
      const fileExt = fileName[fileName.length - 1];
      // 校检文件类型
      if (this.fileType) {
        const isTypeOk = this.fileType.indexOf(fileExt) >= 0;
        if (!isTypeOk) {
          this.$modal.msgError(
            `文件格式不正确, 请上传${this.fileType.join('/')}格式文件!`
          );
          return false;
        }
      }
      // 校检文件大小
      if (this.fileSize) {
        let isLt = true;
        let str = '';
        const type = typeof (this.fileSize);
        if (type === 'number') {
          isLt = file.size / 1024 / 1024 < this.fileSize;
          str = `不能超过 ${this.fileSize} MB!`;
        }
        if (type === 'object') {
          const _this = this;
          for (const key in _this.fileSize) {
            if (file.size / 1024 / 1024 > _this.fileSize[key] && this.typeText[key].type.indexOf(fileExt) > 0) isLt = false;
            str += ` ${this.typeText[key].txt}不能超过${_this.fileSize[key]}MB! `;
          }
        }
        if (!isLt) {
          this.$modal.msgError(`上传文件大小${str}`);
          return false;
        } else {
          // 判断文件是否为大文件
          this.isLarge = file.size > 20 * 1024 * 1024;
        }
      }
      this.$modal.loading('正在上传文件，请稍候...');
      this.number++;
      return true;
    },
    // 文件个数超出
    handleExceed() {
      this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);
    },
    // 上传失败
    handleUploadError(err) {
      this.$modal.msgError('上传文件失败，请重试');
      this.$modal.closeLoading();
      console.log(err);
    },

    // 开始上传
    async handleUpload(options) {
      const fileRaw = options.file;
      // 获取md5值
      let fileMd5 = null;
      try {
        fileMd5 = await getFileMd5(fileRaw);
      } catch (e) {
        console.error('[error]', e);
      }
      if (!fileMd5) return;
      // 切片大小
      const chunkSize = 20 * 1024 * 1024;
      // 文件分片储存
      const chunkList = [];
      function chunkPush(page = 1) {
        chunkList.push(fileRaw.slice((page - 1) * chunkSize, page * chunkSize));
        if (page * chunkSize < fileRaw.size) {
          chunkPush(page + 1);
        }
      }
      // 上传前检验文件是否在后台已存在，不存在才进行上传
      const existInfo = await api.checkFile(fileMd5);
      if (existInfo.code === 0 && existInfo.data) {
        this.uploadList.push({ name: existInfo.data.originFileName, url: existInfo.data.url });
        this.$emit('onSuccess', existInfo, fileRaw);
        this.uploadedSuccessfully();
        return;
      } else {
        if (this.isLarge) {
          chunkPush();
          const isSuc = await this.saveFileChunk(chunkList, fileMd5, fileRaw);
          // 全部上传成功后获取文件数据
          if (isSuc) {
            const res = await api.checkFile(fileMd5);
            if (res.code === 0 && res.data) {
              this.uploadList.push({ name: res.data.originFileName, url: res.data.url });
              this.$emit('onSuccess', res, fileRaw);
              this.uploadedSuccessfully();
            }
          }
        } else {
          this.saveCommonFile(fileRaw, fileMd5);
        }
      }
    },

    /**
     * 保存文件至后台(大文件上传)
     * @author liuyc
     * @param {arr} chunkList 分片列表
     * @param {str} fileMd5 文件md5值
     * @param {obj} fileRaw 文件
     * @returns {bool} 是否全部上传成功
     */
    async saveFileChunk(chunkList, fileMd5, fileRaw) {
      let uploadId = '';
      // 记录上传成功的分片数
      let count = 0;
      // 记录回退次数，一个分片重复上传三次都失败则结束上传
      let backCount = 0;
      for (let i = 0; i < chunkList.length; i++) {
        const formData = new FormData();
        formData.append('chunkIndex', i);
        formData.append('totalChunks', chunkList.length);
        formData.append('fileKey', fileMd5);
        formData.append('fileName', fileRaw.name);
        formData.append('contentType', fileRaw.type);
        formData.append('file', chunkList[i]);
        if (i !== 0) {
          formData.append('uploadId', uploadId);
        }
        const res = await this.awaitCatch(api.uploadFile(formData));
        if (res.code === 0) {
          if (i === 0) {
            uploadId = res.data;
          }
          backCount = 0;
          count++;
        } else {
          if (backCount >= 2) {
            backCount = 0;
            this.number--;
            this.$modal.msgError('上传文件失败，请重试');
            break;
          } else {
            i--;
            backCount++;
          }
        }
      }
      this.$modal.closeLoading();
      if (count === chunkList.length) {
        return true;
      } else {
        return false;
      }
    },

    /**
     * 保存文件至后台（普通文件上传）
     * @author liuyc
     * @param {}
     * @return {}
     */
    async saveCommonFile(fileRaw, fileMd5) {
      const formData = new FormData();
      formData.append('chunkIndex', 0);
      formData.append('totalChunks', 1);
      formData.append('fileKey', fileMd5);
      formData.append('fileName', fileRaw.name);
      formData.append('contentType', fileRaw.type);
      formData.append('file', fileRaw);
      const res = await api.uploadCommon(this.interface, formData);
      if (res.code === 0) {
        this.uploadList.push({ name: res.data.originFileName, url: res.data.url });
        this.$emit('onSuccess', res, fileRaw);
        this.uploadedSuccessfully();
      } else {
        this.$modal.closeLoading();
      }
    },

    // 删除文件
    handleDelete(index) {
      this.fileList.splice(index, 1);
      this.$emit('input', this.listToString(this.fileList));
    },

    // 上传结束处理
    uploadedSuccessfully() {
      if (this.number > 0 && this.uploadList.length === this.number) {
        this.fileList = this.fileList.concat(this.uploadList);
        this.uploadList = [];
        this.number = 0;
        this.$emit('input', this.listToString(this.fileList));
        this.$modal.closeLoading();
      }
    },
    // 预览文件(只限video)
    handlePreview(file) {
      this.dialogVisible = true;
      this.$nextTick(() => {
        this.$refs.videoPlay.load();
        this.dialogVideoUrl = file.url;
      });
    },
    // 获取文件名称
    getFileName(name) {
      if (name.lastIndexOf('/') > -1) {
        return name.slice(name.lastIndexOf('/') + 1);
      } else {
        return name;
      }
    },
    // 对象转成指定字符串分隔
    listToString(list, separator) {
      let strs = '';
      separator = separator || ',';
      for (const i in list) {
        strs += list[i].url + separator;
      }
      return strs !== '' ? strs.substr(0, strs.length - 1) : '';
    }
  }
};
</script>

<style scoped lang="scss">
.upload-file-uploader {
  margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
  border: 1px solid #e4e7ed;
  line-height: 2;
  margin-bottom: 10px;
  position: relative;
}
.upload-file-list .ele-upload-list__item-content {
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: inherit;
}
.ele-upload-list__item-content-action .el-link {
  margin-right: 10px;
}
</style>
